/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package my12306;

import java.util.HashMap;
import java.util.List;
import java.util.Random;
import java.util.TimerTask;
import my12306.bean.LeftTickets;
import my12306.bean.PassengerInfo;
import my12306.bean.QueryResult;
import my12306.bean.Tokens;
import my12306.http.Consts;
import my12306.http.TrainInfo;
import org.json.JSONException;
import org.json.JSONObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 *
 * @author Jack
 */
public class ScheduledTask extends TimerTask {

    private final Logger log = LoggerFactory.getLogger(getClass());
    private final TrainClient trainClient = My12306App.getTrainClient();
    private IOptions view;
    private List<PassengerInfo> users;
    private List<TrainInfo> trains;
    private boolean stop = false;
    private final Object waitLock = new Object();
    Random random;

    public ScheduledTask(IOptions view, List<PassengerInfo> users, List<TrainInfo> trains) {
        this.users = users;
        this.trains = trains;
        random = new Random(System.currentTimeMillis());
        this.view = view;
        trainClient.setUserAgent(view.getOptions().getUserAgent().getValue());
        log.info("user-ageng to use: {}", view.getOptions().getUserAgent().getValue());
    }

    @Override
    public void run() {
        log("自动订票开始。");
        log(trainClient.getCookies().replace(";", "<br/>").replace("=", ":<br/>"));
        stop = false;
        boolean failed = false;
        int index = 0;

        if (!trainClient.isLoginOk()) {
            log("请先登录系统。");
            trainClient.checkLogin();
            return;
        }
        boolean checkLeftOk = false;
        while (!isStoped()) {
            TrainInfo bestTrain = null;
            QueryResult qr = null;
            qr = trainClient.querySingleAction_init();
            //todo 11 auto select train if user not select or is empty
//            if (trains == null || trains.isEmpty()) {
//                for (TrainInfo info : allTrains.values()) {
//                    for (PassengerInfo user : users) {
//                        int left = info.checkLeft(user.getSeatType());
//                        if (left > 0) {
//                            trains.add(info);
//                            break;
//                        }
//                    }
//                }
//            }
            do {
                String trainNo = "";
                if (trains != null && trains.size() > 0) {
                    trainNo = trains.get(index).getTrainNo();
                }
                qr =
                        trainClient.querySingleAction_queryLeftTicket(users.get(0).getFromStation(),
                        users.get(0).getToStation(), users.get(0).getStartDate(),
                        users.get(0).getRangDate(), trainNo);
                HashMap<String, TrainInfo> allTrains = qr.getTrains();
                if (allTrains == null || allTrains.isEmpty()) {
                    if (qr.getRawData().contains("<title>登录</title>") || qr.getRawData().equals("-10")) {
                        log("可能超时了，正在处理...");
                        sleepRandom();
                        trainClient.checkLogin();
                    } else {
                        log("未查询到数据。");
                        sleepRandom();
                        continue;
                    }
                }
                //todo: 010 判断乘客选择的席别,乘客数量、余票总量
                bestTrain = allTrains.get(trains.get(index).getTrainCode());
                log(String.format("查询结果： %s", bestTrain.getTrainCode()));
                index++;
                if (index >= trains.size()) {
                    index = 0;
                }
                if (view.getOptions().isCheckTicket()) {
                    checkLeftOk = false;
                    for (PassengerInfo p : users) {
                        int left = bestTrain.checkLeft(p.getSeatType());
                        if (left == TrainInfo.HAVEMANY) {
                            checkLeftOk = true;
                            log(String.format("%s -- %s 票源充足。", p.getName(), p.getSeatTypeName()));
                        } else if (left == TrainInfo.HAVENONE) {
                            checkLeftOk = false;
                            log(String.format("%s -- %s 票已售完。", p.getName(), p.getSeatTypeName()));
                        } else if (left == TrainInfo.HAVENOSUCHSEAT) {
                            checkLeftOk = false;
                            log(String.format("%s -- %s 无此席别。", p.getName(), p.getSeatTypeName()));
                            break;
                        } else if (left == TrainInfo.TIMENOTREACHED) {
                            checkLeftOk = false;
                            log(String.format("%s -- %s 未到起售时间。", p.getName(), p.getSeatTypeName()));
                            break;
                        } else {
                            checkLeftOk = true;
                            log(String.format("%s -- %s 余票: %d。", p.getName(), p.getSeatTypeName(), left));
                        }
                    }
                } else {
                    checkLeftOk = false;
                }

                if (view.getOptions().isCheckTicket() && !checkLeftOk) {
                    log(String.format("%s 不能预订，请检查车次和席别。", bestTrain.getTrainCode()));
                    sleepOptions();
                    continue;
                }


                if (bestTrain.getMeta().isValidate()) {
                    break;
                } else {
                    log("此班列车暂时不能预订。");
                    sleepOptions();
                    continue;
                }
            } while (!isStoped() && (bestTrain == null || !checkLeftOk));
            if (isStoped()) {
                break;
            }
            if (!bestTrain.getMeta().isValidate()) {
                log("此班列车不能预定。");
                break;
            }
            if (!trainClient.isLoginOk()) {
                log("没有登录。");
                break;
//                throw new OrderException("没有登录。");
            }

            //<editor-fold defaultstate="collapsed" desc=" 3. search & book ">                
            if (isStoped()) {
                log("正在停止。");
                break;
            }
            log("开始预定。");
            String id = "" + System.currentTimeMillis();
            String url;
            String ticket;
            String token;
            Tokens tokens;
            String randCode;

            org.json.JSONObject json2 = null;

            StringBuilder sb = new StringBuilder();
            sb.append("你选择的车票是： <br/>").append("<font color='blue'>")
                    .append(bestTrain.getTrainCode()).append("\t发站 ")
                    .append(bestTrain.getFromStation()).append("\t到站 ")
                    .append(bestTrain.getToStation()).append("\r\n 发时 ")
                    .append(bestTrain.getSetoutTime()).append("\t 到时 ")
                    .append(bestTrain.getArriveTime()).append("\t 历时 ")
                    .append(bestTrain.getPeriod()).append("</font>");
            log(sb.toString());

            //<editor-fold defaultstate="collapsed" desc=" step 1 "> 

//            qr = trainClient.querySingleAction_init();
//            tokens = trainClient.confirmPassengerAction_init().getTokensFromUrl();

            //step 1-1  post submutOrderRequest
            url = "https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=submutOrderRequest";
            qr = trainClient.querySingleAction_submutOrderRequest_step1_1("00:00--24:00", bestTrain);
//            trainClient.writetoFile("e:\\test\\" + id + "_1_step1_1", qr.getRawData());
            sleepRandom();
            if (qr.getState() != QueryResult.SUCC) {
                log("1-1: 提交订单可能发生错误。");
                continue;
            }

            if (qr.getRawData().contains("目前您还有未处理的订单")) {
                log("1-1: 目前您还有未处理的订单，\r\n请您到[未完成订单]进行处理!");
                break;
            }

            if (qr.getMsg().contains(bestTrain.getTrainCode())) {
                log("1-1: 提交订单成功。");
            } else {
                log("1-1: 提交订单可能发生错误：\r\n" + qr.getMsg().substring(0, 173));
            }



            // step 1-0 init
//        qr = trainClient.confirmPassengerAction_init();
//        //https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=init
//        token = trainClient.getQueryToken();
//        log("token" + token);
//        if (token == null || token.isEmpty()) {
//            log("token is empty. please retry.");
////            return;
//        }

            //step 1-2  get tokens  
            tokens = trainClient.getTokensFromHtml(qr.getRawData());
            if (tokens.isEmpty()) {
                log("1-2: tokens is empty, getting new tokens...");
                tokens = trainClient.confirmPassengerAction_init().getTokens();
                log("1-2: done.");
//                continue;
            }
//        tokens=trainClient.confirmPassengerAction_init().getTokensFromUrl();
//        qr = trainClient.confirmPassengerAction_init();
//        Tokens tokens2 = trainClient.getTokensFromUrl(referQuerySingleAction);
//        tokens = trainClient.getTokensFromHtml(qr.getRawData());
//        token = trainClient.getQueryToken();//https://dynamic.12306.cn/otsweb/order/querySingleAction.do?method=init
//        tokens.setToken(token);
//        tokens = trainClient.getTokensFromUrl(referQuerySingleAction);
//            log("ticket：" + tokens.getTokensFromUrl());
            if (tokens.isEmpty()) {
                log("1-2: tokens is empty, retrying");
                continue;
            }

            List<LeftTickets> result = LeftTickets.parse(tokens.getTicket());
            for (LeftTickets info : result) {
                log("余票信息: " + info);
            }

//            qr = trainClient.confirmPassengerAction_getpassengerJson();

//        boolean eq = token.equals(tokens.getToken());
//        if (token != null && !token.isEmpty()) {
//            tokens.setToken(token);
//        }
            //</editor-fold>  

            //<editor-fold defaultstate="collapsed" desc=" step 2 ">  
            //step2-1 checkOrderInfo

            do {
                failed = false;
                log("2-1: 请输入验证码:");
                randCode = trainClient.getRandCodeDailog(Consts.URL_passCodeAction_rand_order);
                log("2-1: " + randCode);
                if (randCode == null || randCode.isEmpty()) {
                    log("2-1: 已取消，即将退出。");
                    failed = true;
                    break;
                }
                url = "https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=checkOrderInfo&rand=";
                /**
                 * have to sleepRandom 6 seconds before submit...
                 */
                sleepOptions();
                qr = trainClient.confirmPassengerAction_checkOrderInfo_step2_1(randCode, tokens, users, bestTrain);
                sleepRandom();
                /*"{"checkHuimd":"Y","check608":"Y","msg":"","errMsg":"Y"}" 
                 * {"checkHuimd":"Y","check608":"Y","msg":"","errMsg":"Y"}    
                 * {"checkHuimd":"N","check608":"Y","msg":"对不起，由于您取消次数过多，今日将不能继续受理您的订票请求！","errMsg":"Y"} 
                 * {"checkHuimd":"Y","check608":"Y","msg":"","errMsg":"输入的验证码不正确！"}
                 * "{"errMsg":"验证码错误，请重试！"}"
                 */
//                trainClient.writetoFile("e:\\test\\" + id + "_2_step2_1", qr.getRawData());

                if (qr.getState() != QueryResult.SUCC) {
                    log("2-1: 检查定单错误：" + qr.getMsg());
                    continue;
                }
                if (qr.getRawData().contains("<title>登录</title>")) {
                    log("2-1: 可能超时了，正在重新登录。");
                    trainClient.checkLogin();
                }

                json2 = getJsonObject(qr.getRawData());

                if (getJsonValue(json2, "checkHuimd").equals("N")) {
                    //"对不起，由于您取消次数过多，今日将不能继续受理您的订票请求！
                    log("2-1: " + getJsonValue(json2, "msg"));
                    failed = true;
                    break;
                }

                if (getJsonValue(json2, "check608").equals("N")) {
                    //"本车为实名制列车，实行一日一车一证一票制！
                    log("2-1: " + getJsonValue(json2, "msg"));
                    failed = true;
                    break;
                }

                if (getJsonValue(json2, "errMsg").contains("输入的验证码不正确")
                        || getJsonValue(json2, "errMsg").contains("验证码错误，请重试")) {
                    //输入的验证码不正确！
                    log("2-1: " + getJsonValue(json2, "errMsg"));
                    continue;
                }

                if (getJsonValue(json2, "errMsg").equals("Y")
                        && getJsonValue(json2, "msg").isEmpty()) {
                    //ok, exit loop
                    log("2-1: 检查定单成功。 ");
                    failed = false;
                    break;
                }


            } while (!isStoped());

            if (failed || isStoped()) {
                log(String.format("2-1: %s 准备退出。", getJsonValue(json2, "msg")));
                setStop();//stop = true;
                break;
            }

            //step2-2
            //https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=getQueueCount&train_date=2013-01-27&train_no=6300000L3210&station=L32&seat=1&from=GZQ&to=ZZF&ticket=10169030001016900003

            /*"{"countT":0,"count":1132,"ticket":"10000038001000000001","op_1":true,"op_2":false}"  
             * "{"countT":0,"count":25,"ticket":"10000039561000000000","op_1":true,"op_2":false}"                     * 
             * "{"countT":0,"count":0,"ticket":"","op_1":false,"op_2":true}"
             * "{"countT":0,"count":0,"ticket":"O065600135M1050001059207300012","op_1":false,"op_2":false}"
             * "{"countT":0,"count":0,"ticket":"O065300528M1046001079206400012","op_1":false,"op_2":true}"
             * "{"countT":0,"count":0,"ticket":"O065300748M1046001189206400012","op_1":false,"op_2":false}"
             * "{"countT":0,"count":1,"ticket":"O065300687M1046001179206400012","op_1":false,"op_2":false}"
             */
            sleepRandom();
            qr = trainClient.confirmPassengerAction_getQueueCount_step2_2(bestTrain, users.get(0).getSeatType(), tokens.getTicket());
            sleepRandom();
//            trainClient.writetoFile("e:\\test\\" + id + "_2_step2_2", qr.getRawData());

            if (qr.getState() != QueryResult.SUCC) {
                log("2-2: 检查队列错误" + qr.getMsg());
                continue;
            }
            try {
                json2 = new JSONObject(qr.getRawData());
                if (getJsonValue(json2, "op_2").equals("true")) {
                    //目前排队人数已经超过余票张数，请您选择其他席别或车次，特此提醒。                    
                    log("2-2: 目前排队人数已经超过余票张数，请您选择其他席别或车次，特此提醒。");
                    log("2-2: 正在重新开始。");
                    continue;
                } else {
                    if (getInt(getJsonValue(json2, "countT")) > 0) {
                        log("2-2: " + "目前排队人数： " + getJsonValue(json2, "countT") + " 人，特此提醒。");
                    } else {
                        log("2-2: 检查队列成功。");
                    }
                }

            } catch (JSONException ex) {
                log.error(ex.getLocalizedMessage(), ex);
            }
            if (isStoped()) {
                log("正在停止。");
                break;
            }


            //</editor-fold>

            //<editor-fold defaultstate="collapsed" desc=" step 3 ">    

            //step3-1
            //url = "https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=confirmSingleForQueueOrder";
//            qr = trainClient.confirmPassengerAction_confirmSingleForQueueOrder_step3_1(randCode, tokens, users, bestTrain);
            /*"{"errMsg":"非法的订票请求！"}"   
             * {"errMsg":"Y"}    
             * "{"errMsg":"包含未付款订单","errCode":"FAIL"}"
             //"{"errMsg":"排队人数现已超过余票数，请您选择其他席别或车次。","errCode":"MORE"}"
             * "{"errMsg":"包含排队中的订单","errCode":"FAIL"}"
             * "{"errMsg":"请不要重复提交！"}"
             * "{"errMsg":"验证码错误，请重试！"}"
             */
//            trainClient.writetoFile("e:\\test\\" + id + "_3_step3_1", qr.getRawData());
//            log("3-1: 确认订单:\r\n" + qr.getMsg());
            int tryCount = 2;
            do {
                sleepOptions();
                qr = trainClient.confirmPassengerAction_confirmSingleForQueueOrder_step3_1(randCode, tokens, users, bestTrain);
                sleepRandom();
                tryCount--;
                if (qr.getState() != QueryResult.SUCC) {
                    log("3-1: 确认订单： 可能发生错误。" + qr.getMsg());
                    continue;
                }
                if (qr.getRawData().contains("<title>车票预订</title>") || qr.getRawData().contains("<title>登录</title>")) {
                    log("3-1: 确认订单： 可能发生错误，正在准备重试。");
                    failed = true;
                    continue;
                }
            } while ((qr.getState() != QueryResult.SUCC
                    || !qr.getRawData().contains("{\"errMsg\":\"Y\"}"))
                    && tryCount > 0 && !isStoped());


            if (qr.getState() != QueryResult.SUCC) {
                log("3-1: 确认订单： 可能发生错误。" + qr.getMsg());
                continue;
            }
            if (qr.getRawData().contains("<title>车票预订</title>") || qr.getRawData().contains("<title>登录</title>")) {
                log("3-1: 确认订单： 可能发生错误，正在准备重试。");
                failed = true;
                continue;
            }

            json2 = getJsonObject(qr.getRawData());
            String msg = getJsonValue(json2, "errMsg");
            if (msg.equals("Y")) {
                log("3-1: 确认订单成功，正在准备排队。");
            } else if (msg.contains("非法的订票请求")) {
                log("3-1: " + msg);
                continue;
            } else if (msg.contains("包含未付款订单")) {
                log("3-1: " + msg);
                log("3-1: 请处理完未付款订单后再预订。");
                log("3-1: 准备退出。");
                break;
            } else if (msg.contains("排队人数现已超过余票数")) {
                log("3-1: " + msg);
                continue;
            } else if (msg.contains("包含排队中的订单")) {
                log("3-1: " + msg);
                continue;
            } else if (msg.contains("请不要重复提交")) {
                log("3-1: " + msg);
                trainClient.confirmPassengerAction_cancel(randCode, tokens, users, bestTrain);
                continue;
            } else {
                log("3-1: " + msg);
                continue;
            }

            //step3-2 get order id
            failed = false;
            String orderid = "";
            do {
                //url = "https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=getOrderWaitTime&tourFlag=dc";
                url = "https://dynamic.12306.cn/otsweb/order/myOrderAction.do?method=queryOrderWaitTime&tourFlag=dc";
//                qr = trainClient.doGet(url, referConfirmPassengerAction);
                sleepRandom();
                qr = trainClient.myOrderAction_getOrderWaitTime_3_2(bestTrain, randCode, tokens.getTicket());
                sleepRandom();
                //            /*"{"tourFlag":"dc","waitTime":-2,"waitCount":0,"requestId":5694723412608919774,"errorcode":"0","msg":"对不起，由于您取消次数过多，今日将不能继续受理您的订票请求。1 月10日您可继续使用订票功能。","count":0}"
//             //"{"tourFlag":"dc","waitTime":-1,"waitCount":0,"orderId":"E412621130","requestId":5694347419390027998,"count":0}" 
//             * "{"tourFlag":"dc","waitTime":-1,"waitCount":0,"orderId":"E463163211","requestId":5694315136385788126,"count":0}"  
//             * "{"tourFlag":"dc","waitTime":-1,"waitCount":0,"orderId":"E463163211","requestId":5694315136385788126,"count":0}"
//             * {"tourFlag":"dc","waitTime":5,"waitCount":0,"requestId":5694285137440123102,"count":0}
//             * "{"tourFlag":"dc","waitTime":-2,"waitCount":0,"requestId":5696091164217157854,"msg":"没有足够的票!","count":0}"
//             * "{"tourFlag":"dc","waitTime":-1,"waitCount":0,"orderId":"E471747429","requestId":5696109459540519134,"count":0}"
//             */
                //{"tourFlag":"dc","waitTime":-1,"waitCount":0,"orderId":"E431559501","requestId":5694285137440123102,"count":0}
                //"{"tourFlag":"dc","waitTime":-4}"
//                json = new my12306.http.JSONObject(qr.getRawData());
//                trainClient.writetoFile("e:\\test\\" + id + "_3_step3_3", qr.getRawData());

                if (qr.getState() != QueryResult.SUCC) {
                    log("3-2: 排队发生错误，正在重试。");
                    sleepRandom();
                    continue;
                }

                if (qr.getRawData().contains("<title>车票预订</title>") || qr.getRawData().contains("<title>登录</title>")) {
                    log("3-2: 排队可能发生错误，正在准备重试。");
//                    failed = true;
                    sleepRandom();
                    break;
                }
                json2 = getJsonObject(qr.getRawData());
//                msg = getJsonValue(json2, "errMsg");

//                log("3-2: " + msg);
                orderid = getJsonValue(json2, "orderId");
                if (!orderid.equals("")) {
                    log("3-2: 排队成功，你的订单号是：" + getJsonValue(json2, "orderId"));
                    failed = false;
                    break;
                } else {
                    msg = getJsonValue(json2, "msg");
                    if (msg.contains("没有足够的票")) {
                        log("3-2: " + msg);
                        log("3-2: 正在重试...");
                        failed = true;
                        break;
                    } else if (msg.contains("由于您取消次数过多")) {
                        log("3-2: " + msg);
                        log("3-2: 请停止订单，改日再试。");
                        failed = true;
                        break;
                    }

                    double wait = getInt(getJsonValue(json2, "waitTime")) / 60.0;
                    if (wait > 0) {
                        //todo: 003 排队时间过长时，继续预订其它车次
                        log(String.format("3-2: 排队等待中: %1$.3f 分钟...", wait));
                        sleepRandom();
                        continue;
                    } else {
                        //continue
                        sleepRandom();
                        continue;
                    }
                }


            } while (!isStoped());

            if (failed) {
                continue;
            }
            //step3-3 payorder
            //post https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=payOrder&orderSequence_no=E431559501
            url = "https://dynamic.12306.cn/otsweb/order/confirmPassengerAction.do?method=payOrder&orderSequence_no=";

            sleepRandom();
            qr = trainClient.confirmPassengerAction_payOrder_step3_4(orderid, randCode, tokens, users, bestTrain);
            sleepRandom();
            //席位已成功锁定，请在<b>45</b>分钟内完成网上支付，否则系统将自动取消本次交易。
            //"消息提示 系统忙！ 请点击我的订单确认客票的状态后再尝试后续操作。谢谢！"
//            trainClient.writetoFile("e:\\test\\" + id + "_3_step3_4", qr.getRawData());

            if (qr.getMsg().contains("席位已成功锁定")) {
                log("3-3: 席位已成功锁定，请在<b>45</b>分钟内完成网上支付，否则系统将自动取消本次交易。");
                setStop();//stop = true;
                break;
            } else if (qr.getMsg().contains("系统忙")) {
                log("3-3: 系统忙,正在重试。");
                continue;
            } else {
                log("3-3: 其它信息：\r\n" + qr.getMsg());
                log("3-3: 订票失败，正在重试。");
                continue;
            }

            //</editor-fold>


            //</editor-fold>  

        }
        setStop();//        stop = true;
        log("自动订票结束。\r\n");
    }

    void log(String msg) {
//        log.info(msg);
        view.log(msg);
    }

    int getInt(String str) {
        int result = -1;
        try {
            result = Integer.parseInt(str);
        } catch (Exception ex) {
        }
        return result;
    }

    void continueRun() {
        synchronized (waitLock) {
            waitLock.notifyAll();
        }
    }

    private void waitForSignal() {
        synchronized (waitLock) {
            try {
                waitLock.wait();
            } catch (InterruptedException ie) {
                // continue
            }
        }
    }

    public void setStop() {
//        stop = true;
//        continueRun();
        synchronized (waitLock) {
            stop = true;
            waitLock.notifyAll();
        }
    }

    public boolean isStoped() {
        synchronized (waitLock) {
            return stop;
        }
    }

    String getJsonValue(JSONObject json, String key) {
        String result = "";
        if (json == null) {
            return "";
        }
        try {
            result = json.get(key).toString();
        } catch (Exception ex) {
            result = "";
            log.error(ex.getLocalizedMessage(), ex);
        }

        return result;
    }

    JSONObject getJsonObject(String data) {
        JSONObject result = new JSONObject();
        try {
            result = new JSONObject(data);
        } catch (Exception ex) {
            log.info(data);
            log.error(ex.getLocalizedMessage(), ex);
        }
        return result;
    }

    /**
     * @return the users
     */
    public List<PassengerInfo> getUsers() {
        return users;
    }

    /**
     * @param users the users to set
     */
    public void setUsers(List<PassengerInfo> users) {
        this.users = users;
    }

    /**
     * @return the trains
     */
    public List<TrainInfo> getTrains() {
        return trains;
    }

    /**
     * @param trains the trains to set
     */
    public void setTrains(List<TrainInfo> trains) {
        this.trains = trains;
    }

    @Override
    public String toString() {
        return null;

    }

    void sleepRandom() {
        sleep(Math.abs(random.nextInt() % 1000));
    }

    void sleepOptions() {
        sleep(view.getOptions().getSleepTime() * 1000);
    }

    void sleep(int time) {
        try {
            log(String.format("waiting (%d ms) ...", time));
            Thread.sleep(time);
        } catch (InterruptedException ex) {
            log.error(ex.getLocalizedMessage(), ex);
        }
    }
}
